import IO from 'koa-socket';
import log4js from 'koa-log4';
import rtmIncomingEvents from '../app/rtm/incoming/eventTypes';
import rtmOutgoingEvents from '../app/rtm/outgoing/eventTypes';
import tokenAuth from '../app/middleware/tokenAuth';
import socketUtil from '../app/utils/socket';
import User from '../app/models/user';
import messageEmitter from '../app/rtm/messageEmitter';
import { REDIS_SYSTEM_CHANNEL } from '../app/common/redisChannels';
import { onlineCounter } from './metrics';


function init(app, container) {
  if (!app.callback) return null;
  const redis = container.getRedis();
  const apiBridge = container.getApiBridge();
  const socketLogger = log4js.getLogger('SOCK');
  const onlineStatusLogger = log4js.getLogger('onlineStatus');

  const io = new IO();

  // inject redis and logger
  io.use(async (ctx, next) => {
    ctx.redis = redis;
    ctx.logger = socketLogger;
    ctx.onlineStatusLogger = onlineStatusLogger;
    await next();
  });

  // inject request body
  io.use(async (ctx, next) => {
    ctx.request = {};
    ctx.request.body = ctx.data;
    await next();
  });

  // inject throw function
  io.use(async (ctx, next) => {
    ctx.throw = (msg) => {
      ctx.socket.emit(rtmIncomingEvents.ERROR, msg);
      ctx.logger.error(msg);
      throw new Error(msg);
    };

    await next();
  });

  io.use(async (ctx, next) => {
    if (/disconnect/.test(ctx.event)) {
      await next();
    } else {
      await tokenAuth()(ctx, next);
    }
  });

  io.on('disconnect', async (ctx) => {
    ctx.socket.socket.emit(rtmOutgoingEvents.GOOD_BYE);
    const { userId = 'unknow', token } = socketUtil.getHandshakeQuery(ctx);
    onlineStatusLogger.info(`SOCKET DISCONNECT ${userId} With Token ${token}`);
    // 删除online user
    if (ctx.socket.user) {
      const { jiuquId } = ctx.socket.user;
      const activeSocketId = await redis.hget('user:online:socket', jiuquId);
      const broadcastLogoutNeeded = activeSocketId === ctx.socket.id;

      onlineStatusLogger.info(`SOCKET DISCONNECT BROADCAST ${userId} / ${broadcastLogoutNeeded}`);

      if (broadcastLogoutNeeded) {
        await redis.hdel('user:online', jiuquId);
        await redis.hdel('user:online:socket', jiuquId);

        redis.publish(REDIS_SYSTEM_CHANNEL, JSON.stringify({
          event: rtmOutgoingEvents.USER_LOGOUT,
          payload: ctx.socket.user,
        }));
        onlineCounter.dec();
      }
    }

    // 解除监听
    if (ctx.socket.redis_sub) {
      await ctx.socket.redis_sub.unsubscribe();
      await ctx.socket.redis_sub.disconnect();
      ctx.socket.redis_sub = null;
    }
  });

  messageEmitter.initIncomeMessage((event, fn) => io.on(event, fn));

  /**
   * start server
   */
  io.attach(app);

  // 校验token值,如果token值不合法，向socket抛出一个异常
  app._io.use(async (socket, next) => {
    const { token, role, userId = 'unknow' } = socket.request._query;
    onlineStatusLogger.info(`SOCKET CONNECT INCOME/ ${role} :${userId} / ${token}`);

    const userInfo = await apiBridge.getJiuQuUserByToken(token, role);
    if (!userInfo.id) {
      socketLogger.info(`connect with token ${token} reject.`);
      onlineStatusLogger.info(`SOCKET CONNECT REJECT/ ${role}:${userId} / ${token}`);
      return next(new Error('token-invalid'));
    }

    try {
      const user = await User.ensureJiuQuUser(userInfo, role);

      redis.setex(
        `user:token:${token}`, // key
        7200,
        user.id.toString(), // user_id
      );
      socket.emit(rtmOutgoingEvents.HELLO);
      onlineStatusLogger.info(`SOCKET CONNECT Valid/ ${role}:${userId} / ${token}`);
    } catch (e) {
      onlineStatusLogger.error(`SOCKET CONNECT Error/ ${role}:${userId} / ${token} Error ${e.name} / ${e.message}`);
    }

    return next();
  });
  return io;
}

export default init;
